package br.unicamp.ic.sgct.server.recursos.persistencia;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class BasicCrudDAO<ENTITY, PK> {

    private Class<ENTITY> clazz;
    protected static EntityManagerFactory entityManagerFactory;
    private static Map<Thread, EntityManager> map = new HashMap<Thread, EntityManager>();

    //private EntityTransaction transaction;

    public BasicCrudDAO(Class<ENTITY> clazz) {
        super();
        this.clazz = clazz;


    }

    static {
        //LoaderDB.getInstance().startHSQLDB();
        entityManagerFactory = Persistence.createEntityManagerFactory("SGCTPersistenceUnit");

    }

    public EntityManager getEntityManagerDAO() {
        EntityManager entityManager = map.get(Thread.currentThread());
        if (entityManager == null) {
            entityManager = entityManagerFactory.createEntityManager();
            map.put(Thread.currentThread(), entityManager);
        }else{
            if(!entityManager.isOpen()){
                entityManager = entityManagerFactory.createEntityManager();
                map.put(Thread.currentThread(), entityManager);
            }
        }
        return entityManager;
    }



    public static EntityManager getEntityManager() {
        EntityManager entityManager = map.get(Thread.currentThread());
        return entityManager;
    }

    /**
     * Limpa o Contexto de Persistência (PersistenceContext).
     * Todas entidades passam para o estado DETACHED
     */
    public static void clear() {
        EntityManager entityManager = map.get(Thread.currentThread());
        if(entityManager != null) entityManager.clear();
    }

    /**
     *
     * @param e
     * @return
     */
    public <E>E merge(E e) {
        return getEntityManagerDAO().merge(e);
    }

    /**
     * Fecha o Contexto de Persistência
     */
    public void close() {
        getEntityManagerDAO().close();
    }

    protected Class<ENTITY> getClazzBiz() {
        return clazz;
    }

    protected DAOException getException(Exception ex) {
        return new DAOException();
    }

    @SuppressWarnings(value = "unchecked")
    public List<ENTITY> findAll() throws DAOException {
        LOGGER.debug("create " + getClazzBiz().getCanonicalName()
                + " instance");
        try {
            String str = " FROM " + getClazzBiz().getName();
            List<ENTITY> list = getEntityManagerDAO().createQuery(str).getResultList();
            return list;

        } catch (RuntimeException re) {
            LOGGER.error("findAll failed", re);
            throw getException(re);
        }
    }


    public void beginTransaction(){
        EntityManager em = getEntityManagerDAO();
        EntityTransaction et = em.getTransaction();
        boolean ativar = !et.isActive();
        if (ativar) {
           LOGGER.debug("beginTransaction create transacao");
           et.begin();
        }else{
            LOGGER.debug("beginTransaction transacao jah esta ativa");
        }
    }
    public void commitTransaction(){
        EntityManager em = getEntityManagerDAO();
        EntityTransaction et = em.getTransaction();
        boolean estaAtiva = et.isActive();
        if (estaAtiva) {
           LOGGER.debug("commitTransaction transacao");
           et.commit();
        }else{
            LOGGER.debug("commitTransaction transacao nao esta ativa");
        }
    }
    public void rollbackTransaction(){
        EntityManager em = getEntityManagerDAO();
        EntityTransaction et = em.getTransaction();
        boolean estaAtiva = et.isActive();
        if (estaAtiva) {
           LOGGER.debug("rollbackTransaction rollback transacao");
           et.rollback();
        }else{
            LOGGER.debug("rollbackTransaction transacao nao esta ativa");
        }
    }


    public ENTITY create(ENTITY entity) throws DAOException {
        LOGGER.debug("create " + entity.getClass().getCanonicalName()
                + " instance");
        EntityManager em = getEntityManagerDAO();
        EntityTransaction et = em.getTransaction();
        boolean ativarInterna = !et.isActive();
        try {
            if (ativarInterna) {
                LOGGER.debug("create begin transacao");
                et.begin();
            }
            em.persist(entity);
            //em.flush();
            if (ativarInterna) {
                LOGGER.debug("create comitar transacao");
                et.commit();
            }
            return (ENTITY) entity;
        } catch (RuntimeException re) {
            if (ativarInterna) {
                LOGGER.debug("create rollback transacao");
                et.rollback();
            }
            LOGGER.error("create failed", re);
            throw getException(re);
        }
    }

    public ENTITY save(ENTITY entity, PK pk) throws DAOException {
        LOGGER.debug("save " +  entity + " - pk = " + pk);
        ENTITY retorno = null;

        try {
            if (pk == null) {
                retorno = this.create(entity);
            } else {
                retorno = this.update(entity);
            }
            LOGGER.debug("save successful");
        } catch (RuntimeException re) {
            LOGGER.error("save failed", re);
            throw getException(re);
        }
        return retorno;
    }

    public ENTITY update(ENTITY entity) throws DAOException {
        LOGGER.debug("update " + entity.getClass().getCanonicalName()
                + " instance");
        ENTITY retorno = null;
        EntityManager em = getEntityManagerDAO();
        EntityTransaction et = em.getTransaction();
        boolean ativarInterna = !et.isActive();
        try {
            if (ativarInterna) {
                LOGGER.debug("update begin transacao");
                et.begin();
            }
            retorno = em.merge(entity);
            em.flush();
            if (ativarInterna) {
                LOGGER.debug("update comitar transacao");
                et.commit();
            }
        } catch (RuntimeException re) {
            if (ativarInterna) {
                LOGGER.debug("update rollback transacao");
                et.rollback();
            }

            throw getException(re);
        }
        return retorno;
    }



    public void remove(ENTITY entity) throws DAOException {
        LOGGER.debug("removing " + entity.getClass().getCanonicalName()
                + " instance");

        EntityManager em = getEntityManagerDAO();
        EntityTransaction et = em.getTransaction();
        boolean ativarInterna = !et.isActive();
        try {
            if (ativarInterna) {
                LOGGER.debug("remove begin transacao");
                et.begin();
            }
            em.remove(em.merge(entity));
            //em.flush();
            if (ativarInterna) {
                LOGGER.debug("remove comitar transacao");
                et.commit();
            }

        } catch (RuntimeException re) {
            if (ativarInterna) {
                LOGGER.debug("remove rollback transacao");
                et.rollback();
            }
            LOGGER.error("remove failed", re);
            throw getException(re);
        }
    }

    public ENTITY findById(PK pk) throws DAOException {
        LOGGER.debug("findById " + getClazzBiz().getCanonicalName()
                + " instance");
        try {
            return (ENTITY) getEntityManagerDAO().find(getClazzBiz(), pk);
        } catch (RuntimeException re) {
            LOGGER.error("findById failed", re);
            throw getException(re);
        }
    }

    public Long count() throws DAOException {
        LOGGER.debug("count " + getClazzBiz().getCanonicalName() + " instance");
        try {
            Query query = getEntityManagerDAO().createQuery(
                    "SELECT COUNT(entity) " + "FROM " + getClazzBiz().getName()
                    + " entity ");
            return (Long) query.getSingleResult();
        } catch (RuntimeException re) {
            LOGGER.error("findById failed", re);
            throw getException(re);
        }
    }

    public List<ENTITY> consultaQueryList(String namedQuery, int numMax,
            int offset) throws DAOException {
        return consultaQueryList(namedQuery, numMax, offset);
    }

    @SuppressWarnings("unchecked")
    public List<ENTITY> consultaQueryList(String namedQuery, int numMax,
            int offset, Object... parametros) throws DAOException {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("consultaQueryList "
                    + this.getClass().getCanonicalName());
            LOGGER.debug("  |--- entity \t" + getClazzBiz().getCanonicalName());
            LOGGER.debug("  |--- namedList\t " + namedQuery);
            LOGGER.debug("  |--- parametros\t " + parametros);
        }
        try {
            EntityManager em = this.getEntityManagerDAO();
            Query query = em.createNamedQuery(namedQuery);
            int i = 1;
            if (parametros != null) {
                for (Object object : parametros) {
                    if (object != null) {
                        LOGGER.debug("          |----- parametro " + i + " \t "
                                + object);
                        query.setParameter(i++, object);
                    }
                }
            }
            query.setFirstResult(offset);
            query.setMaxResults(numMax);

            return query.getResultList();
        } catch (RuntimeException re) {
            LOGGER.error("consultaQueryList failed", re);
            throw getException(re);
        }

    }

    @SuppressWarnings("unchecked")
    public List<ENTITY> consultaQueryList(String namedQuery,
            Object... parametros) throws DAOException {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("consultaQueryList "
                    + this.getClass().getCanonicalName());
            LOGGER.debug("  |--- entity \t" + getClazzBiz().getCanonicalName());
            LOGGER.debug("  |--- namedList\t " + namedQuery);
            LOGGER.debug("  |--- parametros\t " + parametros);

        }
        try {
            EntityManager em = this.getEntityManagerDAO();
            Query query = em.createNamedQuery(namedQuery);
            if (parametros != null) {
                int i = 1;
                for (Object object : parametros) {
                    if (object != null) {
                        LOGGER.debug("          |----- parametro " + i + " \t "
                                + object);
                        query.setParameter(i++, object);
                    }
                }
            }
            List<ENTITY> listaEntity = query.getResultList();
            return listaEntity;
        } catch (RuntimeException re) {
            LOGGER.error("consultaQueryList failed", re);
            throw getException(re);
        }

    }

    /**
     * @param namedQuery
     * @param parametros
     * @return
     * @throws java.lang.Exception
     */
    @SuppressWarnings("unchecked")
    public ENTITY consultaQuerySingle(String namedQuery, Object... parametros)
            throws DAOException {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("consultaQueryList "
                    + this.getClass().getCanonicalName());
            LOGGER.debug("  |--- entity \t" + getClazzBiz().getCanonicalName());
            LOGGER.debug("  |--- namedList\t " + namedQuery);
            LOGGER.debug("  |--- parametros\t " + parametros);
        }
        try {
            EntityManager em = this.getEntityManagerDAO();
            Query query = em.createNamedQuery(namedQuery);

            if (parametros != null) {
                int i = 1;
                for (Object object : parametros) {
                    if (object != null) {
                        LOGGER.debug("          |----- parametro " + i + " \t "
                                + object);
                        query.setParameter(i++, object);
                    }
                }
            }
            ENTITY entity = (ENTITY) query.getSingleResult();
            return entity;
        } catch (RuntimeException re) {
            LOGGER.error("consultaQuerySingle failed", re);
            throw getException(re);
        }

    }

    public EntityTransaction getTransaction() {
        return getEntityManagerDAO().getTransaction();
    }

    @Override
    protected final void finalize() throws Throwable {
        //this.entityManager.close();
        super.finalize();
    }
}
